using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using Verse;


namespace RimWorld{

public enum SeedTargFindMode : byte
{
	MapGenCluster,
	Reproduce,
	MapEdge
}

public static class GenPlantReproduction
{
	public static Plant TryReproduceFrom(IntVec3 source, ThingDef plantDef, SeedTargFindMode mode, Map map)
	{
		IntVec3 dest;
		if( !TryFindReproductionDestination(source, plantDef, mode, map, out dest) )
			return null;

		return TryReproduceInto(dest, plantDef, map);
	}

	public static Plant TryReproduceInto(IntVec3 dest, ThingDef plantDef, Map map)
	{
		if( !plantDef.CanEverPlantAt(dest, map) )
			return null;
		
		if( !GenPlant.SnowAllowsPlanting(dest, map) )
			return null;

		return (Plant)GenSpawn.Spawn( plantDef, dest, map );
	}

	public static bool TryFindReproductionDestination(IntVec3 source, ThingDef plantDef, SeedTargFindMode mode, Map map, out IntVec3 foundCell)
	{
		float radius = -1;
		if( mode == SeedTargFindMode.Reproduce )
			radius = plantDef.plant.reproduceRadius;
		else if( mode == SeedTargFindMode.MapGenCluster )
			radius = plantDef.plant.WildClusterRadiusActual;
		else if( mode == SeedTargFindMode.MapEdge )
			radius = 40;

		//Gather some data about the area around source
		//Note: This scans a square, though we only reproduce into a circle
		int numFoundPlants = 0;
		int numFoundPlantsMyDef = 0;
		float totalFertility = 0;
		var rect = CellRect.CenteredOn( source, Mathf.RoundToInt(radius) );
		rect.ClipInsideMap(map);
		for (int z = rect.minZ; z <= rect.maxZ; z++)
		{
			for (int x = rect.minX; x <= rect.maxX; x++)
			{
				var c = new IntVec3(x,0,z);
				var p = c.GetPlant(map);
				if( p != null )
				{
					numFoundPlants++;

					if( p.def == plantDef )
						numFoundPlantsMyDef++;
				}

				totalFertility += c.GetTerrain(map).fertility;
			}
		}

		//Determine theoretical number of desired plants of any type
		float numDesiredPlants = totalFertility * map.Biome.plantDensity;
		bool full = numFoundPlants > numDesiredPlants;
		bool overloaded = numFoundPlants > numDesiredPlants * 1.25f;

		if( overloaded )
		{
			foundCell = IntVec3.Invalid;
			return false;
		}

		//Determine num desired plants of my def
		var curBiome = map.Biome;
		float totalCommonality = curBiome.AllWildPlants.Sum( pd=>curBiome.CommonalityOfPlant(pd) );
		float minProportion = curBiome.CommonalityOfPlant(plantDef) / totalCommonality;
		float maxProportion = (curBiome.CommonalityOfPlant(plantDef) *plantDef.plant.wildCommonalityMaxFraction) / totalCommonality;

		//Too many plants of my type nearby - don't reproduce
		float maxDesiredPlantsMyDef = numDesiredPlants * maxProportion;
		if( numFoundPlantsMyDef > maxDesiredPlantsMyDef )
		{
			foundCell = IntVec3.Invalid;
			return false;
		}

		//Too many plants nearby for the biome/total fertility - don't reproduce
		//UNLESS there are way too few of my kind of plant
		float minDesiredPlantsMyDef = numDesiredPlants * minProportion;
		bool desperateForPlantsMyDef = numFoundPlantsMyDef < minDesiredPlantsMyDef * 0.5f;
		if( full && !desperateForPlantsMyDef )
		{
			foundCell = IntVec3.Invalid;
			return false;
		}

		//We need to plant something
		//Try find a cell to plant into
		Predicate<IntVec3> destValidator = c=>
		{
			if( !plantDef.CanEverPlantAt(c, map) )
				return false;

			if( !GenPlant.SnowAllowsPlanting(c, map) )
				return false;

			if( !source.InHorDistOf( c, radius) )
				return false;

			if( !GenSight.LineOfSight( source, c, map, skipFirstCell: true ) )
				return false;

			return true;
		};
		return CellFinder.TryFindRandomCellNear( source,
											map,
											Mathf.CeilToInt(radius),
											destValidator,
											out foundCell );
	}
}}

